using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using NGridDrawer.Partitions;
using System;

namespace NGridDrawer.Draw
{
    /// <LastUpdate author="Blaise Braye" Date="15/06/2008 17:06"/>
    /// <summary>
    /// This <see cref="IPartitionDrawer"/> is able to render a <see cref="DataGridView"/> using its native drawing methods
    /// </summary>
    /// <remarks>
    /// The problem using natives methods of a <see cref="DataGridView"/> is that when drawing cells, 
    /// it uses the static <see cref="TextRenderer"/> class in which the DrawText methods are not supported for printing.
    /// The documentation proposes to use the DrawString methods of Graphics class instead of it.
    /// It was possible to go through this problem by making some scaling calculation and apply it to
    /// draw methods which are using the <see cref="TextRenderer"/> class. 
    /// This could explain why some border problem may appear on a painted grid.
    /// </remarks>
    public class DataGridViewNativeDrawer : DataGridViewAbstractDrawer
    {
        private static readonly MethodInfo _paintCell =
            typeof (DataGridViewCell).GetMethod("PaintWork", BindingFlags.Instance | BindingFlags.NonPublic);

        /// <summary>
        /// 	<para>Initializes an instance of the <see cref="DataGridViewNativeDrawer"/> class.</para>
        /// </summary>
        /// <param name="gridView">Grid to paint
        /// </param>
        public DataGridViewNativeDrawer(DataGridView gridView) : base(gridView)
        {
        }

        /// <summary>
        /// Initialize the <see cref="IPartitionDrawer"/> instance in order to build a partition
        /// </summary>
        /// <param name="graphic">graphics used to paint</param>
        /// <param name="clipHeights">defines the maximum bounds for height of each partitionLevel</param>
        /// <param name="clipWidths">defines the maximum bounds for width of each partitionColumn</param>
        /// <remarks>
        /// generally, this method is the first call in <see cref="IPartitionDrawer.BuildPartition"/> method.
        /// It is public only because we could need to initialize this to get some information in the grid
        /// without having to build its partition.
        /// </remarks>
        protected override void InitializeDrawerForGrid(System.Drawing.Graphics graphic, float[] clipHeights, float[] clipWidths)
        {
            UnitScale = GetUnitScale(graphic, GridView.Font);
            base.InitializeDrawerForGrid(graphic, clipHeights, clipWidths);
        }

        /// <summary>
        /// Partitions the grid of data into printable areas.
        /// </summary>
        /// <param name="graphic">graphics used to paint</param>
        /// <param name="clipHeights">defines the maximum bounds for height of each partitionLevel</param>
        /// <param name="clipWidths">defines the maximum bounds for width of each partitionColumn</param>
        /// <returns>A partition of the grid to paint</returns>
        /// <seealso cref="NGridDrawer.Partitions.PartitionGrid"/>
        protected override PartitionGrid BuildPartition_P(Graphics graphic, float[] clipHeights, float[] clipWidths)
        {
            return PartitionBuilder.BuildPartition(graphic, clipHeights, clipWidths, PrintOrder, this);
        }



        /// <summary>Get/Set UnitScale Property</summary>
        protected float UnitScale { get; set; }


        /// <summary>
        /// Get the width of a column
        /// </summary>
        /// <param name="graphic">graphics used to paint</param>
        /// <param name="columnKey">index of the interesting column</param>
        /// <returns>float value, width of the interesting column</returns>
        public override float ColumnWidth(Graphics graphic, int columnKey)
        {
            return Mapping.Column(columnKey).Width*Scale;
        }

        

        /// <summary>
        /// Get the width of a [row header] column
        /// </summary>
        /// <param name="graphic">graphics used to paint</param>
        /// <returns>float value, width applied the row headers</returns>
        public override float RowHeaderWidth_P(Graphics graphic)
        {
            return GridView.RowHeadersWidth * Scale;
        }

        /// <summary>
        /// Get the height of a row
        /// </summary>
        /// <param name="graphic">graphics used to paint</param>
        /// <param name="rowKey">index of the interesting row</param>
        /// <returns>float value, height of the interesting row</returns>
        public override float RowHeight(Graphics graphic, int rowKey)
        {
            return Mapping.Row(rowKey).Height*Scale;
        }


        /// <summary>
        /// Calculate the columns header height of the <c>DataGridView</c></summary>
        /// <remarks>
        /// Used by the <c>BuildPartitions</c> method to minimize the clip height of a partition
        /// </remarks>
        /// <param name="graphic">graphics used to render the <c>DataGridView</c></param>
        /// <returns>The height of the columns header of the <c>DataGridView</c></returns>
        protected override float GetHeaderHeight_P(Graphics graphic)
        {
            return GridView.ColumnHeadersHeight*Scale;
        }


        /// <summary>
        /// Compute partition bounds
        /// </summary>
        /// <param name="location">indicates proposed start position of partition</param>
        /// <param name="element">partitionElement which must be painted</param>
        /// <returns>Partition bounds</returns>
        protected override RectangleF GetPartitionBounds(PointF location, PartitionElement element)
        {
            Rectangle bounds = Rectangle.Truncate(new RectangleF(location, element.Size));
            return new RectangleF(bounds.X, bounds.Y, bounds.Width, bounds.Height);
        }

        /// <summary>Draw columns header of the <see cref="DataGridView"/>
        /// </summary>
        /// <param name="graphic">graphics used to paint</param>
        /// <param name="bounds">
        /// Bounds area where the <paramref name="element"/> can be drawn
        /// </param>
        /// <param name="element">partitionElement to paint</param>
        /// <returns>
        /// Bottom Position of the Columns Header
        /// </returns>
        protected override float DrawColumnsHeader(Graphics graphic, RectangleF bounds, PartitionElement element)
        {
            int colKey = element.Column.FirstColumn;
            int colIndex = Mapping.ColumnIndex(colKey);

            float headerHeigth = GetHeaderHeight(graphic);

            var unitRec = getUnitScaledRec(
                bounds.X + RowHeaderWidth(graphic), 
                bounds.Y, 
                ColumnWidth(graphic, colKey), 
                headerHeigth);

            bool isFirstColumn = true;
            bool isLastColumn = false;


            do
            {
                var rec = getRec(unitRec);


                OnElementDrawing(Mapping.Column(colKey).HeaderCell, rec);

                unitRec = DrawCell(graphic, bounds, unitRec, -1, colIndex, isFirstColumn, isLastColumn);

                OnElementDrawn(Mapping.Column(colKey).HeaderCell, rec);

                if (++colKey <= element.Column.LastColumn)
                {
                    colIndex = Mapping.ColumnIndex(colKey);
                    unitRec.X = unitRec.Right;
                    unitRec.Width = ColumnWidth(graphic, colKey)*UnitScale;
                    isFirstColumn = false;
                    if (colKey == element.Column.LastColumn)
                        isLastColumn = true;
                }
            } while (colKey <= element.Column.LastColumn);


            return headerHeigth + bounds.Top;
        }


        private RectangleF getRec(RectangleF unitScaleRec)
        {
            return new RectangleF(
                unitScaleRec.X / UnitScale,
                unitScaleRec.Y / UnitScale,
                unitScaleRec.Width / UnitScale,
                unitScaleRec.Height / UnitScale);
        }

        private RectangleF getUnitScaledRec(float x,float y,float width,float height)
        {
            return new RectangleF(
                x * UnitScale,
                y * UnitScale,
                width * UnitScale,
                height * UnitScale);
        }


        /// <summary>
        /// Draw the containment of the <paramref name="element"/> in the specified <paramref name="graphics"/>
        /// starting at <paramref name="currentY"/> y coordinate
        /// </summary>
        /// <param name="graphics">graphics used to paint</param>
        /// <param name="clipBounds">
        /// Bounds area where the <paramref name="element"/> can be drawn
        /// </param>
        /// <param name="element">partitionElement to paint</param>
        /// <param name="currentY">
        /// Top coordinate of the <paramref name="element"/> in <paramref name="graphics"/>
        /// </param>
        /// <returns>
        /// Bottom Position of the Partition element
        /// </returns>
        protected override float DrawRows(Graphics graphics, RectangleF clipBounds, PartitionElement element, float currentY)
        {
            currentY *= UnitScale;

            
            var headerwidth = RowHeaderWidth(graphics);

            for (int rowKey = element.Level.FirstRow; rowKey <= element.Level.LastRow; rowKey++)
            {
                OnElementDrawing(Mapping.Row(rowKey),clipBounds);

                int rowIndex = Mapping.RowIndex(rowKey);
                float currentX = clipBounds.X * UnitScale;

                #region draw header cell

                
                RectangleF unitRecHeader;
                if (headerwidth > 0)
                {
                    var rowheader = Mapping.Row(rowKey).HeaderCell;

                    unitRecHeader =
                        new RectangleF(
                            currentX,
                            currentY,
                            headerwidth * UnitScale,
                            RowHeight(graphics, rowKey) * UnitScale + 1);

                    var rec = getRec(unitRecHeader);

                    OnElementDrawing(rowheader, rec);

                    unitRecHeader = DrawCell(graphics, clipBounds, unitRecHeader, rowIndex, rowheader.ColumnIndex, true, false);

                    OnElementDrawn(rowheader, rec);

                    currentX += headerwidth * UnitScale;
                }



                #endregion


                

                int colKey = element.Column.FirstColumn;

                DataGridViewCell cell = Mapping.Cell(rowKey, colKey);

                cell.Selected = false;

                var unitRec =
                    new RectangleF(
                        currentX,
                        currentY,
                        ColumnWidth(graphics, colKey)*UnitScale,
                        RowHeight(graphics, rowKey)*UnitScale);

                //bool isFirstColumn = true;
                bool isLastColumn = false;

                do
                {
                    var rec = getRec(unitRec);

                    OnElementDrawing(cell, rec);

                    unitRec = DrawCell(graphics, clipBounds, unitRec, rowIndex, cell.ColumnIndex, false, isLastColumn);

                    OnElementDrawn(cell, rec);

                    if (++colKey <= element.Column.LastColumn)
                    {
                        cell = Mapping.Cell(rowKey, colKey);
                        cell.Selected = false;
                        unitRec.X = unitRec.Right;
                        unitRec.Width = ColumnWidth(graphics, colKey)*UnitScale;
                        //isFirstColumn = false;
                        if (colKey == element.Column.LastColumn)
                            isLastColumn = true;
                    }
                } while (colKey <= element.Column.LastColumn);

                currentY = (float) Math.Truncate(unitRec.Bottom); // fixes border drawing

                OnElementDrawn(Mapping.Row(rowKey), clipBounds);
            }

            return currentY;
        }



        /// <summary>Draw a <see cref="DataGridViewCell"/> object in <see cref="Graphics"/> object
        /// </summary>
        /// <param name="graphics">graphics used to paint</param>
        /// <param name="clipBounds">Clipping area of the cell</param>
        /// <param name="paintAreaF">Painting area of the cell to draw</param>
        /// <param name="rowIndex">Row index of the cell to draw</param>
        /// <param name="columnIndex">Column index of the cell to draw</param>
        /// <param name="isFirstColumn">
        /// Is it the first column of the partitionElement which is currently drawn
        /// </param>
        /// <param name="isLastColumn">
        /// Is it the last column of the partitionElement which is currently drawn
        /// </param>
        /// <returns>
        /// The bounds area used by the cell in <paramref name="graphics"/>
        /// </returns>
        public RectangleF DrawCell(Graphics graphics, RectangleF clipBounds,
                                   RectangleF paintAreaF, int rowIndex, int columnIndex, bool isFirstColumn,
                                   bool isLastColumn)
        {
            DataGridViewCell cell = GetCell(rowIndex, columnIndex);

            var borderStyle = GetBorderStyle(cell, isFirstColumn, isLastColumn);

            var cellStyle =
                new DataGridViewCellStyle((cell.HasStyle) ? cell.Style : cell.InheritedStyle);

            cellStyle.Font =
                new Font(
                    cellStyle.Font.FontFamily,
                    cellStyle.Font.SizeInPoints*UnitScale*Scale,
                    cellStyle.Font.Style);

            // cast purpose, you should ensure paintAreaF is already filled with natural value
            Rectangle paintArea = Rectangle.Truncate(paintAreaF); 

            

            if (columnIndex >= 0)
            {
                NativePaintCell(
                    graphics,
                    rowIndex,
                    cell,
                    cellStyle,
                    clipBounds,
                    paintArea,
                    borderStyle,
                    DataGridViewPaintParts.ContentForeground |
                    DataGridViewPaintParts.ContentBackground |
                    DataGridViewPaintParts.Background);
            }
            else {
                //paintArea = GetContentBounds(cell, paintArea, borderStyle, isFirstColumn, isLastColumn);
                
                GraphicsUnit originUnit = graphics.PageUnit;
                graphics.PageUnit = GraphicsUnit.Pixel;

                NativePaintCell(
                    graphics,
                    rowIndex,
                    cell,
                    cellStyle,
                    clipBounds,
                    paintArea,
                    borderStyle,
                    DataGridViewPaintParts.ContentBackground |
                    DataGridViewPaintParts.Background);

                graphics.PageUnit = originUnit;

                NativePaintCell(
                    graphics,
                    rowIndex,
                    cell,
                    cellStyle,
                    clipBounds,
                    paintArea,
                    borderStyle,
                    DataGridViewPaintParts.ContentForeground);



            }


            if (rowIndex >= 0)
            {
               
                GraphicsUnit originUnit = graphics.PageUnit;
                graphics.PageUnit = GraphicsUnit.Pixel;

                NativePaintCell(
                    graphics,
                    rowIndex,
                    cell,
                    (cell.HasStyle) ? cell.Style : cell.InheritedStyle,
                    clipBounds,
                    paintArea,
                    borderStyle,
                    DataGridViewPaintParts.Border);
                graphics.PageUnit = originUnit;
            }

            return paintArea;
        }


        /// <summary>
        /// </summary>
        /// <param name="graphic">graphics used to paint</param>
        /// <param name="font">
        /// </param>
        /// <returns>
        /// </returns>
        protected static float GetUnitScale(Graphics graphic, Font font)
        {
            GraphicsUnit originUnit = graphic.PageUnit;

            float sourceHeight = font.GetHeight(graphic);

            graphic.PageUnit = GraphicsUnit.Pixel;

            float targetHeight = font.GetHeight(graphic);

            graphic.PageUnit = originUnit;

            return targetHeight/sourceHeight;
        }


        /// <summary>
        /// This method is a static interface which enable us to used the internal drawing feature of <see cref="DataGridView"/> classes
        /// </summary>
        /// <param name="graphic">graphics uses to paint</param>
        /// <param name="rowIndex">Row index of the cell to draw</param>
        /// <param name="cell"><see cref="DataGridViewCell"/> object to draw</param>
        /// <param name="cellStyle">Style to apply to the <paramref name="cell"/></param>
        /// <param name="clipBounds">Clipping area of the cell</param>
        /// <param name="paintArea">Painting area of the cell to draw</param>
        /// <param name="borderStyle">
        /// Border style to apply around the <paramref name="cell"/> when it is drawn
        /// </param>
        /// <param name="parts">Border parts to draw around the cell</param>
        protected static void NativePaintCell(Graphics graphic, int rowIndex, DataGridViewCell cell,
                                              DataGridViewCellStyle cellStyle, RectangleF clipBounds, Rectangle paintArea,
                                              DataGridViewAdvancedBorderStyle borderStyle, DataGridViewPaintParts parts)
        {
            _paintCell.Invoke(cell,
                              new object[]
                                  {
                                      graphic,
                                      Rectangle.Truncate(clipBounds),
                                      paintArea,
                                      rowIndex,
                                      DataGridViewElementStates.Visible,
                                      cellStyle,
                                      borderStyle,
                                      parts
                                  });
        }

        private DataGridViewCell GetCell(int rowIndex, int columnIndex)
        {
            if (rowIndex < 0)
            {
                return GridView.Columns[columnIndex].HeaderCell;
            }
            else if (columnIndex < 0)
            {
                return GridView.Rows[rowIndex].HeaderCell;
            }
            else
            {
                return GridView[columnIndex, rowIndex];
            }
        }

        private DataGridViewAdvancedBorderStyle GetBorderStyle(DataGridViewCell cell, bool isFirstColumn, bool isLastColumn)
        {

            DataGridViewAdvancedBorderStyle borderStyle = null;

            if (cell.RowIndex < 0)
            {
                borderStyle = GridView.AdvancedColumnHeadersBorderStyle;
            }
            else if (cell.ColumnIndex < 0)
            {
                borderStyle = GridView.AdvancedRowHeadersBorderStyle;
                borderStyle.Bottom = DataGridViewAdvancedCellBorderStyle.None;
            }
            else
            {
                borderStyle = GridView.AdvancedCellBorderStyle;
            }



            borderStyle =
                cell.AdjustCellBorderStyle(borderStyle,
                                           new DataGridViewAdvancedBorderStyle(), false, false, isFirstColumn,
                                           isLastColumn);

            if (isFirstColumn && cell.RowIndex >= 0)
                borderStyle.Left = borderStyle.Right;
            return borderStyle;

        }



        private Rectangle GetContentBounds(
            DataGridViewCell cell,
            Rectangle paintArea,
            DataGridViewAdvancedBorderStyle borderStyle, bool isFirstColumn, bool isLastColumn)
        {
            var rec = Rectangle.FromLTRB(paintArea.Left, paintArea.Top, paintArea.Right, paintArea.Bottom);

            rec.X += Width(borderStyle.Left);
            rec.Y -= Width(borderStyle.Top);
            rec.Width -= Width(borderStyle.Right);
            rec.Height -= Width(borderStyle.Bottom);

            return rec;

        }

        private int Width(DataGridViewAdvancedCellBorderStyle style)
        {
            int width = 0;
            switch (style)
            {
                case DataGridViewAdvancedCellBorderStyle.Inset:
                    width = 1;
                    break;
                case DataGridViewAdvancedCellBorderStyle.InsetDouble:
                    width = 2;
                    break;
                case DataGridViewAdvancedCellBorderStyle.None:
                    width = 0;
                    break;
                case DataGridViewAdvancedCellBorderStyle.NotSet:
                    width = 0;
                    break;
                case DataGridViewAdvancedCellBorderStyle.Outset:
                    width = 1;
                    break;
                case DataGridViewAdvancedCellBorderStyle.OutsetDouble:
                    width = 2;
                    break;
                case DataGridViewAdvancedCellBorderStyle.OutsetPartial:
                    width = 0;
                    break;
                case DataGridViewAdvancedCellBorderStyle.Single:
                    width = 1;
                    break;
                default:
                    break;
            }
            return width;
        }
    }
}